package org.xtext.runtime.util;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.xtext.runtime.term.ITerm;
import org.xtext.runtime.term.Variable;
import org.xtext.runtime.type.TypeException;


public class Bindings {
	public static final Bindings EMPTY_BINDINGS = new Bindings();
	
	private Map<String, Variable> bindings = new HashMap<String, Variable>();
	
	public void addVariable(Variable variable) throws BindingException, TypeException {
		// First check if the variable is already in the binding set. If it
		// is, then throw an exception indicating a duplicate.
		if (bindings.containsKey(variable.identifier())) {
			throw new BindingException("Duplicate Variable declared: " + variable);
		}
		
		// Now apply the variable to all the current variable bindings to remove
		// variable chains.
		for (Variable var : bindings.values()) {
			if (variable.equals(var.value())) {
				var.assign(variable.value());
			}
		}
		
		// Now add the variable o the set of bindings
		bindings.put(variable.identifier(), variable);
	}
	
	public boolean hasVariable(Variable variable) {
		return bindings.containsKey(variable.identifier());
	}
	
	public ITerm getStoredValue(Variable variable) {
		Variable var = bindings.get(variable.identifier());
		if (var != null) return var.value();
		return null;
	}
	
	public String toString() {
		return bindings.values().toString(); 
	}

	public Bindings merge(Bindings b) {
		Bindings set = new Bindings();
		for (Variable variable : bindings.values()) {
			try {
				set.addVariable(variable.copy());
			} catch (BindingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (TypeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for (Variable variable : b.bindings.values()) {
			try {
				set.addVariable(variable.copy());
			} catch (BindingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (TypeException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return set;
	}
	
	public Bindings copy() {
		Bindings b = new Bindings();
		for (Variable variable : bindings.values()) {
			try {
				b.addVariable((Variable) variable.copy());
			} catch (BindingException e) {
				return null;
			} catch (TypeException e) {
				return null;
			}
		}
		return b;
	}

	public Bindings filter(Set<Variable> variables) throws BindingException {
		Bindings b = new Bindings();
		for (Variable variable : variables) {
			try {
				if (variable.isFree()) {
					Variable v = bindings.get(variable.identifier());
					if (v != null) b.addVariable(v);
					else throw new BindingException("Expected variable: " + variable);
				}
			} catch (TypeException e) {
				return null;
			}
		}
		return b;
	}

	public void reduce() {
		for (Variable variable : bindings.values()) {
			if (Variable.class.isInstance(variable.value())) {
				Variable var = (Variable) variable.value();
				if (!var.isFree()) {
					try {
						variable.assign(var.value());
					} catch (TypeException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
		
	}
	
	public boolean equals(Bindings obj) {
		if (obj.bindings.size() != bindings.size()) return false;
		
		for (Entry<String, Variable> entry : bindings.entrySet()) {
			Variable value = obj.bindings.get(entry.getKey());
			if (value == null) return false;
			if (!entry.getValue().equals(value)) return false;
		}
		
		return true;
	}

	public void addAll(Bindings temp) throws BindingException, TypeException {
		for (Variable variable : temp.bindings.values()) {
			addVariable(variable);
		}
		
	}
}
